package com.xc.quickstart.infrastructure.ds;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.plumelog.core.util.GfJsonUtil;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

@Configuration
@EnableTransactionManagement
@MapperScan("com.xc.quickstart.mapper")
@Slf4j
public class MybatisPlusConfig {

    @Autowired
    private DataSourceProperties dataSourceProperties;

    @Value("${mybatis-plus.configuration.log-impl:}")
    private String logImpl;

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        mybatisSqlSessionFactoryBean.setDataSource(routingDataSource());
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        mybatisSqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:com/xc/quickstart/mapper/xml/*Mapper.xml"));
        mybatisSqlSessionFactoryBean.setPlugins(mybatisPlusInterceptor());

        MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();

        if (StringUtils.isNotEmpty(logImpl)) {
            mybatisConfiguration.setLogImpl((Class<? extends Log>) Class.forName(logImpl));
        }

        mybatisConfiguration.setMapUnderscoreToCamelCase(true);
        mybatisSqlSessionFactoryBean.setConfiguration(mybatisConfiguration);

        return mybatisSqlSessionFactoryBean.getObject();
    }

    @Bean
    public AbstractRoutingDataSource routingDataSource() {
        DynamicDataSource proxy = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        DataSourceEnum[] dataEnums = DataSourceEnum.values();
        Arrays.stream(dataEnums).forEach(item -> {
                    targetDataSources.put(item.getModule(), this.getDataSource(item));
                }
        );

        DataSource defaultDataSource = this.getDataSource(DataSourceEnum.TCPOI_WRITE);
        targetDataSources.put(DataSourceEnum.TCPOI_WRITE.getModule(), defaultDataSource);
        proxy.setDefaultTargetDataSource(defaultDataSource);
        proxy.setTargetDataSources(targetDataSources);
        return proxy;
    }

    /**
     * 获取数据源
     *
     * @param dataSourceEnum
     * @return
     */
    private DataSource getDataSource(DataSourceEnum dataSourceEnum) {
        Optional<DataSourceConfig> optional = dataSourceProperties.getDatabase().stream().filter(config -> StrUtil.equalsIgnoreCase(config.getModule()
                , dataSourceEnum.getModule())).findFirst();

        if (!optional.isPresent()) {
            log.error("===can not find the db config:{}", GfJsonUtil.toJSONString(dataSourceEnum));
            return null;
        }

        DataSourceConfig dbConfig = optional.get();
        return getDataSource(dataSourceEnum.getScheme(), dbConfig.getHost(), dbConfig.getPort(), dbConfig.getUsername(), dbConfig.getPassword());
    }

    /**
     * 构造数据源
     *
     * @param scheme
     * @param host
     * @param port
     * @param user
     * @param pwd
     * @return
     */
    private DataSource getDataSource(String scheme, String host, Integer port, String user, String pwd) {
        String url = MessageFormat.format(DataSourceConfig.template, host, String.valueOf(port), scheme);
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(url);
        config.setUsername(user);
        config.setPassword(pwd);
        config.setDriverClassName(DataSourceConfig.driver);
        config.addDataSourceProperty("cachePrepStmts", "true");
        config.addDataSourceProperty("prepStmtCacheSize", "250");
        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
        config.addDataSourceProperty("useServerPrepStmts", "true");
        config.addDataSourceProperty("useLocalSessionState", "true");
        config.addDataSourceProperty("useLocalTransactionState", "true");
        config.addDataSourceProperty("rewriteBatchedStatements", "true");
        config.addDataSourceProperty("cacheResultSetMetadata", "true");
        config.addDataSourceProperty("cacheServerConfiguration", "true");
        config.addDataSourceProperty("elideSetAutoCommits", "true");
        config.addDataSourceProperty("maintainTimeStats", "false");

        config.setConnectionTimeout(30_000);
        config.setIdleTimeout(600_000);
        config.setMaxLifetime(180_0000);
        return new HikariDataSource(config);
    }

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
//        interceptor.addInnerInterceptor(new IllegalSQLInnerInterceptor());
        return interceptor;
    }
}
